package com.hup.sokoban.service.common;

import com.hup.utils.commons.start.SpringContext;
import com.hup.utils.commons.thread.ThreadPoolUtil;
import com.hup.utils.commons.thread.ThreadUtil;
import lombok.extern.log4j.Log4j2;

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 自动播放[胜出记录]服务类,管理功能的[启用,禁用],[开始,停止]
 * <pre>
 * 启用和禁用的切换场景
 *      .[游戏胜出]:启用功能
 *      .[加载通关记录]:启用功能,开始自动播放
 *      .[切换关卡,重玩,读取进度,删除通关记录]:停止并禁用自动播放功能
 * 开始和停止的切换场景
 *      .[加载通关记录]:开始自动播放
 *      .[用户拖拽进度条,撤销/恢复快捷键]:停止自动播放
 *      .[点击自动播放复选框]:开始/停止自动播放
 *      .禁用[游戏键(方向键)]响应
 *      .展示[自动播放复选框]
 *
 * [启用/禁用]时的响应
 *      .恢复/禁用[游戏键(方向键)]响应
 *      .显示/隐藏[只读图标]
 *      .显示/隐藏[自动播放复选框]
 * </pre>
 *
 * @author hugan
 * @date 2020/5/24
 */
@Log4j2
public class AutoplayWinningService {

    public static void bind(AutoplayPlayer player, AutoplayObserver... observers) {
        new AutoplayWinningService(player, Arrays.asList(observers));
    }

    private final AutoplayPlayer player;
    private final List<AutoplayObserver> observers;
    private final ThreadPoolExecutor executor;

    private boolean enabled;
    private PlayerTask playerTask;
    private final AudioService audioService;

    private AutoplayWinningService(AutoplayPlayer player, List<AutoplayObserver> observers) {
        this.player = player;
        this.observers = observers;
        executor = ThreadPoolUtil.fixedThreadExecutor("AutoplayWinning-", 1, "自动播放器");
        for (AutoplayObserver observer : observers) observer.setAutoplayWinningService(this);
        audioService = SpringContext.getBean(AudioService.class);
    }

    public boolean isEnabled() {
        return enabled;
    }

    public boolean isPlaying() {
        return playerTask != null && !playerTask.finished;
    }

    /**
     * [启用,禁用]功能
     */
    public void setEnable(boolean enable) {
        log.info("enable={}", enable);
        this.enabled = enable;
        observers.forEach(observer -> observer.onAutoplayEnabled(enable));
        if (playerTask != null) {
            playerTask.cancelAndWait();
        }
    }

    public void setPlayState(boolean start) {
        if (!enabled) {
            //没启用时,无操作
            return;
        }
        //observer.onAutoplaySwitchedState是在task起止时发出通知的
        if (!start) {
            if (playerTask != null) playerTask.setCanceled();
        } else {
            if (playerTask == null) {
                playerTask = new PlayerTask();
            } else {
                playerTask.cancelAndWait();
            }
            executor.execute(playerTask);
        }
    }

    private class PlayerTask implements Runnable {
        /**
         * 步数播放间隔ms
         */
        private static final int SLEEP_SPAN = 80;
        private final ReentrantLock lock = new ReentrantLock();
        private final Condition stepCondition = lock.newCondition();
        private final Condition finishCondition = lock.newCondition();

        private boolean canceled;
        private boolean finished = true;

        @Override
        public void run() {
            log.debug("开始播放");
            lock.lock();
            canceled = false;
            finished = false;
            lock.unlock();
            observers.forEach(observer -> observer.onAutoplaySwitchedState(true));

            int total = player.beforeStart();
            for (int i = 0; i < total; i++) {
                try {
                    lock.lock();
                    log.debug("sleep.st");
                    stepCondition.await(SLEEP_SPAN, TimeUnit.MILLISECONDS);
                    log.debug("sleep.ed,canceled={}", canceled);
                    if (canceled) break;
                } catch (InterruptedException e) {
                    finished = true;
                    ThreadUtil.handleInterruptedException(e);
                } finally {
                    log.debug("unlock");
                    lock.unlock();
                }

                boolean end = player.playNext();
                audioService.move();
                if (end) break;
            }

            lock.lock();
            player.afterFinished(canceled);
            observers.forEach(observer -> observer.onAutoplaySwitchedState(false));//要通知ui
            finished = true;
            finishCondition.signalAll();
            log.debug("unlock.st");
            lock.unlock();
            log.debug("unlock.ed");
            log.debug("播放结束, canceled={}", canceled);
        }

        public void setCanceled() {
            try {
                lock.lock();
                if (finished) return;
                log.info("");
                this.canceled = true;
                stepCondition.signalAll();
            } finally {
                lock.unlock();
            }
        }

        public void cancelAndWait() {
            try {
                log.info("");
                lock.lock();
                log.debug("check.in, finished={}", finished);
                if (finished) {
                    //这里会有问题,finally中补救
                    return;
                }

                canceled = true;
                log.debug("signalAll");
                stepCondition.signalAll();
                log.debug("await.st");
                finishCondition.await();
                log.debug("await.ed");
            } catch (InterruptedException e) {
                ThreadUtil.handleInterruptedException(e);
            } finally {
                log.debug("unlock");
                lock.unlock();
                while (executor.getActiveCount() != 0) {
                    /*
                    run方法中:
                        unlock后,仍要执行'耗时代码'(即使是log代码,占用小于1ms)
                    会有机率导致
                        .[executor线程还没结束,本方法就return了(因为run末尾已经finish并unlock了)
                        .继而setPlayState=true方法中,executor.execute(playerTask)会有异常!(提交了2条线程的任务量)
                    即使unlock后,不执行'耗时代码',理论上仍会有机率出现此异常!
                        .不加入本while代码块前,很容易重现异常
                        .log.info在[触发写入本地文件]时'比较耗时';换成了debug后就基本上不会出现了,但仍然需要本代码块护航
                    比较直接简单的方式是executor线程数设为2
                        .因为任务线程unlock后就没有业务功能了
                        .但本代码块是为了解决/实现'cancelAndWait'
                     */
                    log.info("等待线程结束!");//日志有打印,代表重现上述问题
                    ThreadUtil.sleep(2);
                }
            }
        }
    }

    /**
     * 播放器,实现播放功能
     */
    public interface AutoplayPlayer {
        /**
         * @return 总步数, 只是作为参考值, 实际播放步数与 playNext的返回值相关
         */
        int beforeStart();

        /**
         * 播放下一步
         *
         * @return 是否已到最后一步
         */
        boolean playNext();

        /**
         * 播放已结束
         *
         * @param canceled true-被停止,可能未播放完;false-播放结束
         */
        @SuppressWarnings("unused")
        default void afterFinished(boolean canceled) {}
    }

    /**
     * 状态观察者,实现[通知,被通知]ui刷新
     */
    public interface AutoplayObserver {

        void setAutoplayWinningService(AutoplayWinningService service);

        /**
         * 功能被[启用,禁用]时触发
         */
        void onAutoplayEnabled(boolean enabled);

        /**
         * 自动播放被[开始,停止]
         */
        default void onAutoplaySwitchedState(boolean start) {
        }

    }

}
